package com.easybbs.aspect;

import com.easybbs.annotation.Globallnterceptor;
import com.easybbs.annotation.VerifyParam;
import com.easybbs.entity.constants.Constants;
import com.easybbs.entity.dto.SessionWebUserDto;
import com.easybbs.entity.dto.SysSettingDto;
import com.easybbs.entity.enums.DateTimePatternEnum;
import com.easybbs.entity.enums.ResponseCodeEnum;
import com.easybbs.entity.enums.UserOperFrequencyTypeEnum;
import com.easybbs.entity.po.ForumArticle;
import com.easybbs.entity.query.ForumArticleQuery;
import com.easybbs.entity.query.ForumCommentQuery;
import com.easybbs.entity.query.LikeRecordQuery;
import com.easybbs.entity.vo.ResponseVO;
import com.easybbs.exception.BusinessException;
import com.easybbs.service.ForumArticleService;
import com.easybbs.service.ForumCommentService;
import com.easybbs.service.LikeRecordService;
import com.easybbs.utils.*;
import org.apache.commons.lang3.ArrayUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Date;

@Component
@Aspect
public class OperactionAspect {
    private static final Logger logger = LoggerFactory.getLogger(OperactionAspect.class);

    private static final String[] TYPE_BASE = {"java.lang.String", "java.lang.Integer", "java.lang.Long"};

    @Autowired
    private ForumArticleService forumArticleService;

    @Autowired
    private ForumCommentService forumCommentService;

    @Autowired
    private LikeRecordService likeRecordService;

    @Pointcut("@annotation(com.easybbs.annotation.Globallnterceptor)") //使用自定义aop Globallnterceptor拦截器
    private void requestInterceptor() {

    }

    @Around("requestInterceptor()")
    public Object interceptorDo(ProceedingJoinPoint joinPoint) throws BusinessException {
        try {
            Object target = joinPoint.getTarget();
            Object[] arguments = joinPoint.getArgs(); //获取使用这个Globallnterceptor拦截器注解的请求方法参数
            String methodName = joinPoint.getSignature().getName(); //获取使用这个Globallnterceptor拦截器注解的请求方法名称
            Class<?>[] parameterTypes = ((MethodSignature) joinPoint.getSignature()).getMethod().getParameterTypes();
            Method method = target.getClass().getMethod(methodName, parameterTypes);
            Globallnterceptor interceptor = method.getAnnotation(Globallnterceptor.class); //这里获取到的就是我们使用的Globallnterceptor拦截器注解
            if (null == interceptor) {
                return null;
            }
            //interceptor里面的是我们自定义的校验拦截方法
            if (interceptor.checkLogin()) {
                checkLogin();
            }
            /**
             * 校验参数
             */
            if (interceptor.checkParams()) {
                validateParams(method, arguments);
            }

            /**
             * 频次校验
             */
            chekcFrequency(interceptor.frequencyType());

            //具体去执行哪个接口的方法
            Object pointResult = joinPoint.proceed();

            /**
             * 校验发帖没超频次 数量加1 超过不走
             */
            if (pointResult instanceof ResponseVO) {
                ResponseVO responseVO = (ResponseVO) pointResult;
                if (Constants.STATUC_SUCCESS.equals(responseVO.getStatus())) {
                    addOpCount(interceptor.frequencyType());
                }
            }

            //返回结果
            return pointResult;

        } catch (BusinessException e) {
            logger.error("全局拦截器异常：{}", e);
            throw e;
        } catch (Exception e) {
            logger.error("全局拦截器异常：{}", e);
            throw new BusinessException(ResponseCodeEnum.CODE_500);
        } catch (Throwable e) {
            logger.error("全局拦截器异常：{}", e);
            throw new BusinessException(ResponseCodeEnum.CODE_500);
        }
    }

    /**
     * 添加频次数量
     *
     * @param typeEnum
     */
    private void addOpCount(UserOperFrequencyTypeEnum typeEnum) {
        if (typeEnum == null || typeEnum == UserOperFrequencyTypeEnum.NO_CHECK) {
            return;
        }
        //拿取全局的session
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        HttpSession session = request.getSession();
        String curDate = DateUtil.format(new Date(), DateTimePatternEnum.YYYY_MM_DD.getPattern()); //当前日期
        //将频次信息存到session中 某一个人 某一天 可以做多少次操作
        String sessionKey = Constants.SESSION_KEY_FREQUENCY + curDate + typeEnum.getOperType();
        Integer count = (Integer) session.getAttribute(sessionKey);

        session.setAttribute(sessionKey, count + 1);
    }


    /**
     * 频次校验的方法
     *
     * @param typeEnum
     */
    void chekcFrequency(UserOperFrequencyTypeEnum typeEnum) {
        if (typeEnum == null || typeEnum == UserOperFrequencyTypeEnum.NO_CHECK) {
            return;
        }
        //拿取全局的session
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        HttpSession session = request.getSession();
        SessionWebUserDto webUserDto = (SessionWebUserDto) session.getAttribute(Constants.SESSION_KEY);

        String curDate = DateUtil.format(new Date(), DateTimePatternEnum.YYYY_MM_DD.getPattern()); //当前日期
        //将频次信息存到session中 某一个人 某一天 可以做多少次操作
        String sessionKey = Constants.SESSION_KEY_FREQUENCY + curDate + typeEnum.getOperType();
        Integer count = (Integer) session.getAttribute(sessionKey);
        SysSettingDto sysSettingDto = SysCacheUtils.getSysSetting();

        //校验什么类型的频次
        switch (typeEnum) {
            case POST_ARTICLE:
                if (count == null) {
                    ForumArticleQuery forumArticleQuery = new ForumArticleQuery();
                    forumArticleQuery.setUserId(webUserDto.getUserId());
                    forumArticleQuery.setPostTimeStart(curDate);
                    forumArticleQuery.setPostTimeEnd(curDate);
                    count = forumArticleService.findCountByParam(forumArticleQuery);
                }
                if (count >= sysSettingDto.getPostSetting().getPostDayCountThreshold()) {
                    throw new BusinessException(ResponseCodeEnum.CODE_602);
                }
                break;
            case POST_COMMENT:
                if (count == null) {
                    ForumCommentQuery commentQuery = new ForumCommentQuery();
                    commentQuery.setUserId(webUserDto.getUserId());
                    commentQuery.setPostTimeStart(curDate);
                    commentQuery.setPostTimeEnd(curDate);
                    count = forumCommentService.findCountByParam(commentQuery);
                }
                if (count >= sysSettingDto.getCommentSetting().getCommentDayCountThreshold()) {
                    throw new BusinessException(ResponseCodeEnum.CODE_602);
                }
                break;
            case DO_LIKE:
                if (count == null) {
                    LikeRecordQuery likeRecordQuery = new LikeRecordQuery();
                    likeRecordQuery.setUserId(webUserDto.getUserId());
                    likeRecordQuery.setCreateTimeStart(curDate);
                    likeRecordQuery.setCreateTimeEnd(curDate);
                    count = likeRecordService.findCountByParam(likeRecordQuery);
                }
                if (count >= sysSettingDto.getLikeSetting().getLikeDayCountThreshold()) {
                    throw new BusinessException(ResponseCodeEnum.CODE_602);
                }
                break;
            case IMAGE_UPLAOD:
                if (count == null) {
                    count = 0;
                }
                if (count >= sysSettingDto.getPostSetting().getDayImageUploadCount()) {
                    throw new BusinessException(ResponseCodeEnum.CODE_602);
                }
                break;
        }
        session.setAttribute(sessionKey,count);
    }

    /**
     * 校验用户是否登录的方法
     */
    private void checkLogin() {
        //获取request请求
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        //获取请求的session
        HttpSession session = request.getSession();
        //获取session中的用户信息
        Object obj = session.getAttribute(Constants.SESSION_KEY);
        //如果没有登录
        if (null == obj) {
            throw new BusinessException(ResponseCodeEnum.CODE_901);
        }
    }

    /**
     * 校验参数的方法
     *
     * @param m
     * @param arguments
     */
    private void validateParams(Method m, Object[] arguments) {
        Parameter[] parameters = m.getParameters();
        for (int i = 0; i < parameters.length; i++) {
            Parameter parameter = parameters[i];
            // 请求方法的参数值 是一个对象
            Object value = arguments[i];
            //获取参数类型的注解 例如："java.lang.String"
            VerifyParam verifyParam = parameter.getAnnotation(VerifyParam.class);
            if (verifyParam == null) {
                continue;
            }
            if (ArrayUtils.contains(TYPE_BASE, parameter.getParameterizedType().getTypeName())) {
                checkValue(value, verifyParam);
            } else {

            }
            //拿到全部的参数数据 但是没有参数类型 需要在定义一个注解来获取参数类型
//            logger.info(JsonUtils.convertObj2Json(value));
        }
    }

    /**
     * 校验传递的参数是不是一个对象
     *
     * @param parameter
     * @param value
     */
    public void checkObjValue(Parameter parameter, Object value) {

    }

    /**
     * 校验传递的参数值
     *
     * @param value
     * @param verifyParam
     */
    private void checkValue(Object value, VerifyParam verifyParam) {
        //判断传递的参数是否为空
        Boolean isEmpty = value == null || StringTools.isEmpty(value.toString());
        //判断参数长度
        Integer length = value == null ? 0 : value.toString().length();
        /**
         * 校验空
         */
        if (isEmpty && verifyParam.required()) {
            throw new BusinessException(ResponseCodeEnum.CODE_600);
        }
        /**
         * 判断长度
         */
        if (!isEmpty && verifyParam.max() != -1 && verifyParam.max() < length || verifyParam.min() != -1 && verifyParam.min() > length) {
            throw new BusinessException(ResponseCodeEnum.CODE_600);
        }
        /**
         * 校验正则
         */
        if (!isEmpty && !StringTools.isEmpty(verifyParam.regex().getRegex()) && !VerifyUtils.verify(verifyParam.regex(), String.valueOf(value))) {
            throw new BusinessException(ResponseCodeEnum.CODE_600);
        }
    }
}
